package ooo.reindeer.commons;

import ooo.reindeer.commons.utils.XmlUtils;
import ooo.reindeer.data.util.CloneUtil;
import ooo.reindeer.io.UnicodeInputStream;

import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static ooo.reindeer.commons.utils.IOUtils.release;

/**
 * 应用属性
 * <p>
 * 属性读取优先级
 * 命令行属性&gt;系统属性&gt;环境变量&gt;应用当前目录属性文件&gt;资源路径属性文件
 * 高优先级属性覆盖低优先级属性
 * <p>
 * 多环境配置 java -jar xxx.jar --profiles.active=&lt;ActiveName&gt;.
 * 启用后对应属性文件:application[-&lt;ActiveName&gt;].properties
 * 不指定环境配置时可以没有application.properties属性文件
 * <p>
 * 命令行属性 java -jar xxx.jar --&lt;PropertyName&gt;=[PropertyValue]...
 * String属性"--" 开头： --&lt;PropertyName&gt;=[PropertyValue]
 * boolean属性：--&lt;PropertyName&gt; 读取时值为true
 * <p>
 * 系统属性 System.PropertyLoader()
 * java自动获取系统属性
 * 命令行赋值String属性-D&lt;PropertyName&gt;=[PropertyValue]
 * <p>
 * 环境变量 System.getenv()
 * 系统及环境变量
 * <p>
 * 应用当前目录属性文件
 * 对应路径 System.getProperty("user.dir") 或 java -jar xxx.jar --profiles.path=&lt;profiles.path&gt;
 * 如果指定了profiles.active 那么对应属性文件必须存在
 * 属性文件名称：application[-&lt;profiles.active&gt;].properties
 * <p>
 * 资源路径属性文件
 * 对应路径 Properties.class.getResource("/")
 * 如果指定了profiles.active 并且 application.properties 存在那么对应属性文件必须存在
 * 属性文件名称：application[-&lt;profiles.active&gt;].properties
 * <p>
 * 支持属性中引用直接引用属性 ${PropertyName},运行期间会替换为属性值
 * &lt;PropertyNameX&gt;=PropertyValueX
 * &lt;PropertyNameY&gt;=${PropertyNameX}-ok  解析后：&lt;PropertyNameY&gt;=PropertyValueX-ok
 * <p>
 * 支持自动属性随机值
 * &lt;PropertyName&gt;=${random.value} 32位随机字符串:415a29f9e5dd38e23f2d4fd39e79a821
 * &lt;PropertyName&gt;=${random.uuid} uuid随机字符串:505a3dd5-7742-4a51-bac9-e14291582e49
 * &lt;PropertyName&gt;=${random.long} 随机long值正数:0-MaxLongValue
 * &lt;PropertyName&gt;=${random.int} 随机int值正数:0-MaxIntValue
 * &lt;PropertyName&gt;=${random.int[MaxBound&gt;]} 随机int值正数:0-MaxBound
 * &lt;PropertyName&gt;=${random.int[&lt;MiniBound&gt;,&lt;MaxBound&gt;]} 随机int值正数:MiniBound-MaxBound
 */
public class Property implements Serializable {

    public static final String TYPE_XML = "XML";
    public static final String TYPE_XML_STRING = "XML_STRING";
    public static final String TYPE_PROPERTIES_XML = "PROPERTIES_XML";
    public static final String TYPE_PROPERTIES_XML_STRING = "PROPERTIES_XML_STRING";
    public static final String TYPE_PROPERTIES = "PROPERTIES";
    public static final String TYPE_PROPERTIES_STRING = "PROPERTIES_STRING";


    public static final String ENCODING = System.getProperty("property.encoding", "AUTO");

    private static final String PROPERTY_PROFILES_ACTIVE = "profiles.active";
    private static final String SPRING_PROPERTY_PROFILES_ACTIVE = "spring.profiles.active";
    private static final String SPRING_PROPERTY_PROFILES_PATH = "spring.profiles.path";
    private static final String PROPERTY_PROFILES_PATH = "profiles.path";

    private static final String REGEX = "\\.";
    private static final String EMPTY_STRING = "";
    private static final Map<String, Object> DATA = new ConcurrentHashMap<>();
    private static final Property DEFAULT_PROPERTY = new Property();
    private static final Property DEFAULT_EMPTY_PROPERTY = new Property(null, Collections.unmodifiableMap(new ConcurrentHashMap<>()));
    private static String PROFILES_ACTIVE = null;
    //Priority Max
    private static Properties CMD_PARAMS = paresCmdParams();
    //Priority Max-1
    private static Properties SYS_PROPERTIES = getProperties(System.getProperties());
    //Priority Max-2
    private static Properties SYS_ENV = getProperties(System.getenv());
    //Priority Max-3
    private static Properties APPLICATION_PROPERTIES = getApplicationProperties(
            CMD_PARAMS.getProperty(PROPERTY_PROFILES_PATH,
                    CMD_PARAMS.getProperty(SPRING_PROPERTY_PROFILES_PATH, "")), "application.properties");
    //Priority Max-4
    private static Properties CUSTOMER_PROPERTIES = new Properties();
    //Priority Mini
    private static Properties CLASS_PROPERTIES = getClassPathProperties("application.properties");
    private static Properties FINAL_PROPERTIES;
    private static Map<String, Map<String, ICalculate>> VALUE_FUN = new HashMap<>();
    Map<String, ICalculate> calculates = new HashMap<String, ICalculate>();

    static {
        initProperty();
    }

    private Map<String, Object> subData = DATA;
    private String path = null;

    private Property(String path, Map<String, Object> data) {
        this.subData = data;
        this.path = path;
    }

    private Property() {
//        this.subData = new ConcurrentHashMap<String, Object>();
//        this.path="";
    }

    private Property(Map<String, Object> map) {
        this.subData = map;
        this.path = "";
    }


    public static Property build() {
        return new Property(new ConcurrentHashMap<>());
    }

    public static Property build(Map<String, Object> map) {
        return new Property(map);
    }

    public static Property build(Properties properties) {
        Property property = build();
        property.initSubData(properties);
        return property;
    }

    public static Property buildModifiedProperty(Property property) {

        return CloneUtil.clone(property);
    }

    public static Property build(String propertyFilePath) {
        return build(propertyFilePath, TYPE_PROPERTIES);
    }

    public static Property buildFrom(String content, String fileType) {
        Property newProperty = Property.build();
        if (fileType.equals(TYPE_XML)) {
            try {
                Properties classProperty = null;
                Properties property = null;
                try {
                    classProperty = mapToProperties(XmlUtils.MapOlny(XmlUtils.XML2MAP(Property.class.getClassLoader().getResource(content).getFile())));
                } catch (Exception e) {
                }
                try {
                    property = mapToProperties(XmlUtils.MapOlny(XmlUtils.XML2MAP(content)));
                } catch (Exception e) {
                }

                if (classProperty == null && property == null) {
                    throw new RuntimeException(fileType + ":" + content + " invalid");
                }


                newProperty.initOnlySubData((property == null) ? new Properties() : property, (classProperty == null) ? new Properties() : classProperty);

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else if (fileType.equals(TYPE_XML_STRING)) {
            try {
                newProperty.initOnlySubData(mapToProperties(XmlUtils.MapOlny(XmlUtils.XMLString2MAP(content))),new Properties());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else if (fileType.equals(TYPE_PROPERTIES_STRING)) {
            try {
                Properties properties = new Properties();
                properties.load(new StringReader(content));
                newProperty.initOnlySubData(properties,new Properties());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else if (fileType.equals(TYPE_PROPERTIES_XML_STRING)) {
            try {
                Properties properties = new Properties();
                properties.loadFromXML(new ByteArrayInputStream(content.getBytes()));
                newProperty.initOnlySubData(properties,new Properties());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else if (fileType.equals(TYPE_PROPERTIES_XML)) {
            try {
                Properties properties = new Properties();
                properties.loadFromXML(new FileInputStream(content));
                Properties classProperties = new Properties();
                classProperties.loadFromXML(Property.class.getClassLoader().getResourceAsStream(content));
                newProperty.initOnlySubData(properties, classProperties);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else {
            throw new UnsupportedOperationException(fileType);
        }

        return newProperty;
    }


    public static Property build(String content, String fileType) {
        Property newProperty = Property.build();
        if (fileType.equals(TYPE_XML)) {
            try {
                Properties classProperty = null;
                Properties property = null;
                try {
                    classProperty = mapToProperties(XmlUtils.MapOlny(XmlUtils.XML2MAP(Property.class.getClassLoader().getResource(content).getFile())));
                } catch (Exception e) {
                }
                try {
                    property = mapToProperties(XmlUtils.MapOlny(XmlUtils.XML2MAP(content)));
                } catch (Exception e) {
                }

                if (classProperty == null && property == null) {
                    throw new RuntimeException(fileType + ":" + content + " invalid");
                }


                newProperty.initSubData((property == null) ? new Properties() : property, (classProperty == null) ? new Properties() : classProperty);

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else if (fileType.equals(TYPE_XML_STRING)) {
            try {
                newProperty.initSubData(mapToProperties(XmlUtils.MapOlny(XmlUtils.XMLString2MAP(content))));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else if (fileType.equals(TYPE_PROPERTIES_STRING)) {
            try {
                Properties properties = new Properties();
                properties.load(new StringReader(content));
                newProperty.initSubData(properties);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else if (fileType.equals(TYPE_PROPERTIES_XML_STRING)) {
            try {
                Properties properties = new Properties();
                properties.loadFromXML(new ByteArrayInputStream(content.getBytes()));
                newProperty.initSubData(properties);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else if (fileType.equals(TYPE_PROPERTIES_XML)) {
            try {
                Properties properties = new Properties();
                properties.loadFromXML(new FileInputStream(content));
                Properties classProperties = new Properties();
                classProperties.loadFromXML(Property.class.getClassLoader().getResourceAsStream(content));
                newProperty.initSubData(properties, classProperties);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else if (fileType.equals(TYPE_PROPERTIES)) {
            newProperty.loadPropertiesFile(content);
        } else {
            throw new UnsupportedOperationException(fileType);
        }

        return newProperty;
    }

    private static Properties getApplicationProperties(String path, String propFile) {

        String propertiesPath = path;
        if (propertiesPath == null || propertiesPath.isEmpty()) {
            propertiesPath = System.getProperty("user.dir");

        }

        Properties properties = new Properties();
        try {


            File file = new File(propertiesPath + File.separatorChar + propFile);
            if (!(file.exists() || file.isFile())) {
                propertiesPath += File.separatorChar + "config";
            }

            properties = getProperties(propertiesPath + File.separatorChar + propFile);
        } catch (Exception e) {
        }

        if (PROFILES_ACTIVE == null) {
            PROFILES_ACTIVE = properties.getProperty(PROPERTY_PROFILES_ACTIVE, properties.getProperty(SPRING_PROPERTY_PROFILES_ACTIVE, null));
        }
        if (PROFILES_ACTIVE == null) {
            return properties;
        } else {
            try {
                Properties activeProperties = getProperties(propertiesPath + File.separatorChar + propFile.replace(".properties", "-") + PROFILES_ACTIVE + ".properties");
                properties = mergeProperties(properties, activeProperties);
            } catch (Exception e) {
                throw new RuntimeException(propertiesPath + File.separatorChar + propFile.replace(".properties", "-") + PROFILES_ACTIVE + ".properties Exception.", e);
            }
            return properties;
        }
    }


    private void initSubData(Properties properties) {
        initSubData(properties, new Properties());
    }

    private static Properties getClassPathProperties(String path) {
        Properties properties = new Properties();
        InputStream inputStream = null;
        InputStreamReader reader = null;
        try {
            inputStream = Property.class.getClassLoader().getResourceAsStream(path);
            reader = getRealCharSetInputStream(inputStream);
            properties.load(reader);
        } catch (Exception e) {
        } finally {
            release(reader, inputStream);
        }
        if (PROFILES_ACTIVE == null) {
            PROFILES_ACTIVE = properties.getProperty(PROPERTY_PROFILES_ACTIVE, properties.getProperty(SPRING_PROPERTY_PROFILES_ACTIVE, null));
        }
        if (PROFILES_ACTIVE == null) {

            return properties;
        } else {
            try {
                inputStream = Property.class.getClassLoader().getResourceAsStream(path.replace(".properties", "-") + PROFILES_ACTIVE + ".properties");
                reader = getRealCharSetInputStream(inputStream);
                Properties activeProperties = new Properties();
                activeProperties.load(reader);
                properties = mergeProperties(properties, activeProperties);

            } catch (Exception e) {
            } finally {
                release(reader, inputStream);
            }
            return properties;
        }
    }

    private static Properties mapToProperties(Map map) {
        Properties properties = new Properties();
        String keyPath = "";
        mapToProperties(map, properties, keyPath);
        return properties;

    }

    private static Properties getProperties(String path) throws Exception {
        Properties properties = new Properties();
        InputStream inputStream = null;

        try {
            inputStream = new FileInputStream(path);

            properties.load(getRealCharSetInputStream(inputStream));

            return properties;
        } catch (Exception e) {
            throw e;
        } finally {
            release(inputStream);
        }
    }

    private static Properties analyticVariable(Properties prop) {
        Set<Object> keys = prop.keySet();
        for (Object srcKey : keys) {
            for (Object destKey : keys) {
                prop.setProperty((String) destKey, prop.getProperty((String) destKey).replace("${" + srcKey + "}", prop.getProperty((String) srcKey, "")));
            }

        }
        return prop;
    }

    private static <V> V covertValue(V defaultValue, String value) {

        if (defaultValue instanceof String) {
            return (V) value.trim();
        }
        if (defaultValue instanceof Boolean) {
            return (V) (Boolean.valueOf(value.trim()));
        }
        if (defaultValue instanceof Byte) {
            return (V) (Byte.valueOf(value.trim()));
        }
        if (defaultValue instanceof Short) {
            return (V) (Short.valueOf(value.trim()));
        }
        if (defaultValue instanceof Integer) {
            return (V) (Integer.valueOf(value.trim()));
        }
        if (defaultValue instanceof Long) {
            return (V) (Long.valueOf(value.trim()));
        }
        if (defaultValue instanceof Float) {
            return (V) (Float.valueOf(value.trim()));
        }
        if (defaultValue instanceof Double) {
            return (V) (Double.valueOf(value.trim()));
        }

        return (V) value;
    }

    private static Properties decryptValues(Properties prop) {
        String jepp = prop.getProperty("jasypt.encryptor.property.prefix", "ENC(");
        String jeps = prop.getProperty("jasypt.encryptor.property.suffix", ")");
        String jep = prop.getProperty("jasypt.encryptor.password", "");

        Set<Object> keys = prop.keySet();
        for (Object srcKey : keys) {
            String value = prop.getProperty((String) srcKey, "");
            if (value.startsWith(jepp) && value.endsWith(jeps)) {

                value = value.substring(jepp.length(), value.lastIndexOf(jeps));

                value = Enc.ENC.compute(value, jep);

            }
            prop.setProperty((String) srcKey, value);
        }
        return prop;
    }

    private static Properties generatingDynamicValues(Properties prop) {
        Set<Object> keys = prop.keySet();


        for (Object srcKey : keys) {

            String value = prop.getProperty((String) srcKey, "");


            Pattern p = Pattern.compile("\\$\\{[a-zA-Z0-9\\.\\,\\(\\)\\[\\]\\ ]*\\}");
            Matcher matcher = p.matcher(value);
            while (matcher.find()) {
                String temp = matcher.group();
                String[] paramInfo = new String[0];
                String[] claInfo = (temp.substring(2, temp.length() - 1)).split("\\.");
                if (claInfo.length < 2) {
                    continue;
                }
                String cla = claInfo[0];
                String[] funInfo = claInfo[1].split("[\\(\\[]");
                if (funInfo.length < 1) {
                    continue;
                }
                String fun = funInfo[0];
                if (funInfo.length > 1 && !funInfo[1].isEmpty()) {
                    paramInfo = funInfo[1].split("[\\,\\]\\)]");
                }
                Map<String, ICalculate> claM = VALUE_FUN.get(cla);
                if (claM == null) {
                    String tempValue = prop.getProperty(temp.substring(2, temp.length() - 1), "");
                    if (tempValue.isEmpty()) {
                        tempValue = prop.getProperty(temp.substring(2, temp.length() - 1).replace('.', '_'), "");
                    }
                    value = value.replace(temp, tempValue);
                } else {
                    ICalculate funM = claM.get(fun);
                    if (funM == null) {
                        continue;
                    }
                    value = value.replace(temp, funM.compute(paramInfo));
                }

            }
            prop.setProperty((String) srcKey, value);

        }
        return prop;
    }

    /**
     * 获取属性值
     *
     * @param propertyName
     *         属性名
     * @param defaultValue
     *         当值不存在时返回此默认值，默认值不能为null
     * @param <V>
     *         默认值类型
     *
     * @return 根据默认值类型转换并返回属性名对应的值
     */
    public static <V> V get(String propertyName, V defaultValue) {

        String linuxPropertyName = propertyName.replace('.', '_');
        String value = FINAL_PROPERTIES.getProperty(propertyName, null);
        value = (value == null || value.isEmpty()) ? FINAL_PROPERTIES.getProperty(linuxPropertyName, null) : value;
        if (value != null) {
            return covertValue(defaultValue, value);
        } else {
            return defaultValue;
        }
    }

    /**
     * 获取属性值
     *
     * @param propertyName
     *         属性名
     * @param defaultValue
     *         当值不存在时返回此默认值，默认值不能为null
     *
     * @return 返回属性名对应的值
     */
    public static char get(String propertyName, char defaultValue) {
        String linuxPropertyName = propertyName.replace('.', '_');
        String value = FINAL_PROPERTIES.getProperty(propertyName, null);
        value = (value == null || value.isEmpty()) ? FINAL_PROPERTIES.getProperty(linuxPropertyName, null) : value;
        if (value != null) {
            return value.charAt(0);
        } else {
            return defaultValue;
        }
    }

    private static InputStreamReader getRealCharSetInputStream(InputStream src) throws Exception {

        if ("AUTO".equalsIgnoreCase(ENCODING)) {
            try {

                Class codepageDetectorProxyClass = Class.forName("info.monitorenter.cpdetector.io.CodepageDetectorProxy");
                Class parsingDetectorClass = Class.forName("info.monitorenter.cpdetector.io.ParsingDetector");
                Class jChardetFacadeClass = Class.forName("info.monitorenter.cpdetector.io.JChardetFacade");
                Class iCodepageDetectorClass = Class.forName("info.monitorenter.cpdetector.io.ICodepageDetector");
                Object proxy = codepageDetectorProxyClass.getMethod("getInstance").invoke(null);
                codepageDetectorProxyClass.getMethod("add", iCodepageDetectorClass).invoke(proxy, parsingDetectorClass.getConstructor().newInstance());
                codepageDetectorProxyClass.getMethod("add", iCodepageDetectorClass).invoke(proxy, jChardetFacadeClass.getMethod("getInstance").invoke(null));

                Charset charset = (Charset) codepageDetectorProxyClass.getMethod("detectCodepage", InputStream.class, Integer.class).invoke(proxy, src, src.available());

                if (charset != null) {
                    return new InputStreamReader(new UnicodeInputStream(src,charset.toString()), charset);
                } else {
                    return new InputStreamReader(new UnicodeInputStream(src,System.getProperty("file.encoding", "UTF-8")), System.getProperty("file.encoding", "UTF-8"));
                }
            } catch (Exception e) {
                return new InputStreamReader(new UnicodeInputStream(src,System.getProperty("file.encoding", "UTF-8")), System.getProperty("file.encoding", "UTF-8"));
            }
        } else {
            return new InputStreamReader(new UnicodeInputStream(src,ENCODING), ENCODING);

        }
    }

    private static void mapToProperties(Map map, Properties properties, String keyPath) {
        for (Object o : map.entrySet()) {

            Entry<String, Object> entry = (Entry<String, Object>) o;

            if ("".equals(entry.getKey())) {
                properties.put(keyPath, entry.getValue());
            } else {
                mapToProperties((Map) entry.getValue(), properties, (keyPath.isEmpty()) ? entry.getKey() : keyPath + "." + entry.getKey());
            }


        }
    }

    private void initOnlySubData(Properties properties, Properties calssProperties){
        properties = mergeProperties(calssProperties, properties);

        properties = decryptValues(properties);
        properties = generatingDynamicValues(properties);
        properties = analyticVariable(properties);
        properties = replaceUnderline(properties);

        this.subData = new ConcurrentHashMap<>();

        structured(properties, subData);

        this.subData = subData;
    }

    private void initSubData(Properties properties, Properties calssProperties) {
        properties = mergeProperties(CLASS_PROPERTIES, CUSTOMER_PROPERTIES, APPLICATION_PROPERTIES, calssProperties, properties, SYS_ENV, SYS_PROPERTIES, CMD_PARAMS);

        properties = decryptValues(properties);
        properties = generatingDynamicValues(properties);
        properties = analyticVariable(properties);
        properties = replaceUnderline(properties);

        this.subData = new ConcurrentHashMap<>();

        structured(properties, subData);

        this.subData = subData;

    }

    private void loadPropertiesFile(String propertyFilePath) {
        try {

            Properties properties;
            try {
                properties = getApplicationProperties(null, propertyFilePath);
            } catch (Exception e) {
                properties = new Properties();
            }

            Properties calssProperties;
            try {
                calssProperties = getClassPathProperties(propertyFilePath);
            } catch (Exception e) {
                calssProperties = new Properties();
            }

            initSubData(properties, calssProperties);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static Properties getProperties(Properties properties) {
        Set<Entry<Object, Object>> entrySet = properties.entrySet();
        Properties temp = new Properties();

        for (Entry<Object, Object> entry : entrySet) {
            temp.setProperty((String) entry.getKey(), (String) entry.getValue());
        }
        if (PROFILES_ACTIVE == null) {
            PROFILES_ACTIVE = temp.getProperty(PROPERTY_PROFILES_ACTIVE, properties.getProperty(SPRING_PROPERTY_PROFILES_ACTIVE, null));
        }
        return properties;
    }

    private static Properties getProperties(Map<String, String> map) {
        Set<Entry<String, String>> entrySet = map.entrySet();
        Properties properties = new Properties();

        for (Entry<String, String> entry : entrySet) {
            properties.setProperty(entry.getKey(), entry.getValue());
        }
        if (PROFILES_ACTIVE == null) {
            PROFILES_ACTIVE = properties.getProperty(PROPERTY_PROFILES_ACTIVE, properties.getProperty(SPRING_PROPERTY_PROFILES_ACTIVE, null));
        }
        return properties;
    }

    public static Property getPropertyObject() {
        return getPropertyObject(null);
    }

    public static Property getPropertyObject(String keyPath) {
        if (keyPath == null || keyPath.isEmpty()) {
            return DEFAULT_PROPERTY;
        } else {
            return DEFAULT_PROPERTY.getProperty(keyPath);
        }
    }

    private static Map<String, Object> getSubMap(String srcKey, Map<String, Object> temp) {
        String[] subKey = srcKey.split(REGEX);

        for (String key : subKey) {
            Object obj = temp.get(key);
            if (obj == null) {
                obj = new ConcurrentHashMap<String, Object>();
                temp.put(key, obj);
            }
            if (obj instanceof Map) {
                temp = (Map<String, Object>) temp.get(key);
            }
        }
        return temp;
    }

    private static void initProperty() {
        Map<String, ICalculate> calculates = new HashMap<>();
        calculates.put("value", Randoms.VALUE);
        calculates.put("int", Randoms.INT);
        calculates.put("long", Randoms.LONG);
        calculates.put("uuid", Randoms.UUID);
        VALUE_FUN.put("random", calculates);

        Properties tempProperties = mergeProperties(CLASS_PROPERTIES, APPLICATION_PROPERTIES, SYS_ENV, SYS_PROPERTIES, CMD_PARAMS);


        String[] procpertyLoaders = tempProperties.getProperty("ProcpertyLoaders", "").split(",", 0);

        for (String procpertyLoader : procpertyLoaders) {
            if (!procpertyLoader.isEmpty()) {
                try {
                    CUSTOMER_PROPERTIES = mergeProperties(CUSTOMER_PROPERTIES, ((IPropertyLoader) (Class.forName(procpertyLoader).getDeclaredConstructor().newInstance())).getProperties(tempProperties));
                } catch (Throwable e) {
                    throw new RuntimeException(e);
                }
            }

        }

        FINAL_PROPERTIES = mergeProperties(CLASS_PROPERTIES, CUSTOMER_PROPERTIES, APPLICATION_PROPERTIES, SYS_ENV, SYS_PROPERTIES, CMD_PARAMS);
        FINAL_PROPERTIES = decryptValues(FINAL_PROPERTIES);
        FINAL_PROPERTIES = generatingDynamicValues(FINAL_PROPERTIES);
        FINAL_PROPERTIES = analyticVariable(FINAL_PROPERTIES);
        FINAL_PROPERTIES = replaceUnderline(FINAL_PROPERTIES);
        structured(FINAL_PROPERTIES, DATA);
    }

    private static Properties mergeProperties(Properties... props) {
        Properties finalProperties = new Properties();

        for (Properties prop : props) {
            Set<Entry<Object, Object>> entrySet = prop.entrySet();
            for (Entry<Object, Object> entry : entrySet) {
                finalProperties.setProperty(((String) entry.getKey()).trim(), (String) entry.getValue());
            }
        }
        return finalProperties;
    }

    private static Properties paresCmdParams() {
        String commandString = System.getProperty("sun.java.command");
        Properties properties = new Properties();
        if (commandString == null || commandString.isEmpty()) {
            return properties;
        }
        Pattern p = Pattern.compile("/\\s*(\".+?\"|[^:\\s])+((\\s*:\\s*(\".+?\"|[^\\s])+)|)|(\".+?\"|[^\"\\s])+");
        Matcher matcher = p.matcher(commandString);
        String paramStr;
        while (matcher.find()) {
            paramStr = matcher.group();
            if (paramStr.startsWith("--")) {
                String key;
                String value;
                paramStr = paramStr.substring(2);
                int valueIndex = paramStr.indexOf('=');
                if (valueIndex < 0) {
                    key = paramStr;
                    value = "true";
                } else {

                    key = paramStr.substring(0, valueIndex);
                    value = paramStr.substring(valueIndex + 1);

                }
                if (value.startsWith("\"")) {
                    value = value.substring(1);
                }
                if (value.endsWith("\"")) {
                    value = value.substring(0, value.length() - 1);
                }
                properties.setProperty(key, value);
            }
        }
        if (PROFILES_ACTIVE == null) {
            PROFILES_ACTIVE = properties.getProperty(PROPERTY_PROFILES_ACTIVE, properties.getProperty(SPRING_PROPERTY_PROFILES_ACTIVE, null));
        }
        return properties;
    }

    private static void putStructData(String srcKey, Object value, Map<String, Object> temp) {

        getSubMap(srcKey, temp).put(EMPTY_STRING, value);
    }

    private static Properties replaceUnderline(Properties finalProperties) {
        Properties properties = new Properties();
        Set<String> keys = finalProperties.stringPropertyNames();
        for (String srcKey : keys) {
            properties.setProperty(srcKey.replace('_', '.'), finalProperties.getProperty(srcKey));
        }
        return properties;

    }

//    /**
//     * 设置应用内属性值，如果属性已存在将会被覆盖
//     *
//     * @param propertyName
//     *         属性名
//     * @param propertyValue
//     *         属性值
//     *
//     * @return 属性名源值
//     */
//    public static synchronized String set(String propertyName, String propertyValue) {
//        propertyName = propertyName.replace('_', '.');
//        putStructData(propertyName, propertyValue, data);
//        return (String) FINAL_PROPERTIES.setProperty(propertyName, propertyValue);
//    }

    public void set(String propertyName, String propertyValue) {
        putStructData(propertyName, propertyValue, subData);
    }

    private static void structured(Properties properties, Map<String, Object> temp) {

        Set<String> keys = properties.stringPropertyNames();

        for (String srcKey : keys) {
            Object value = properties.getProperty(srcKey);
            putStructData(srcKey, value, temp);
        }
    }

    public Set<String> getKeySet() {
        Set<String> set = new HashSet<>(subData.keySet());
        set.remove(EMPTY_STRING);
        return Collections.unmodifiableSet(set);
    }

    public String getPath() {
        return path;
    }

    public Property getProperty(String keyPath) {
        return getProperty(keyPath, DEFAULT_EMPTY_PROPERTY);
    }

    private <V> V getProperty(String keyPath, V defaultValue) {
        keyPath = keyPath.replace('_', '.');
        String[] subKeys = keyPath.split(REGEX);
        Object temp = subData;
        for (String subKey : subKeys) {
            if (temp instanceof Map) {
                temp = ((Map) temp).get(subKey);
            }
            if (temp == null) {
                break;
            }
        }

        if (temp == null) {

            return defaultValue;

        } else {
            if (temp instanceof String) {
                return covertValue(defaultValue, (String) temp);
            } else if (temp instanceof Map && DEFAULT_EMPTY_PROPERTY.equals(defaultValue)) {
                return (V) (new Property(((path == null) ? keyPath : (path + "." + keyPath)), (Map) temp));
            } else {
                return (V) temp;
            }
        }
    }

    public <V> V getValue(String keyPath, V defaultValue) {
        if(keyPath.isEmpty()) {
            return value(defaultValue);
        }else {
            return getProperty(keyPath).value(defaultValue);
        }
    }

    public <V> V getValue(String keyPath) {
        if(keyPath.isEmpty()){
            return value();
        }else {
            return getProperty(keyPath).value();
        }
    }

    public <V> V getValue() {
        return value();
    }

    public Map<String, Object> toMap() {
        return Collections.unmodifiableMap(subData);
    }

    /**
     * 暂时只支持
     *
     * @return
     */
    public Properties writeAsProperties() {
        return toProperties();
    }

    private Properties toProperties() {
        Properties properties = new Properties();
        Stack<String> pathStack = new Stack<>();
        pathStack.push(path);
        toPropertiesString(subData, pathStack, properties);
        return properties;
    }

    private void toPropertiesString(Map<String, Object> map, Stack<String> pathStack, Properties properties) {
        map.forEach((k, v) -> {
            pathStack.push(k);
            if (v instanceof Map) {
                toPropertiesString((Map) v, pathStack, properties);
            } else {
                StringBuilder property = new StringBuilder();
                for (String s : pathStack) {
                    if (!s.isEmpty()) {
                        property.append(".").append(s);
                    }
                }
                properties.put(property.substring(1), v.toString());

            }
            pathStack.pop();
        });
    }

    @Override
    public String toString() {
        return subData.toString();
    }

    public <V> V value() {
        Object value = subData.get("");
        return covertValue(null, (String) value);
    }

    public <V> V value(V defaultValue) {
        Object value = subData.get("");
        if (value == null) {
            return defaultValue;
        }
        return covertValue(defaultValue, (String) value);
    }


}
